package org.novelhibernate.core.sqlregistry;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import org.novelhibernate.core.ArgumentWrapper;
import org.novelhibernate.core.ParameterMapper;
import org.novelhibernate.core.annotation.PojoForeignEntityDeclaration;
import org.novelhibernate.core.annotation.PojoNonMapDeclaration;
import org.novelhibernate.core.transaction.TransactionSupport;


public abstract class AbstractSQLRegistry extends TransactionSupport{

	static {

		
		if (new File(AbstractSQLRegistry.class.getProtectionDomain()
				.getCodeSource().getLocation().getFile()
				+ "novelhibernate.cfg.xml").exists())
			try {
				
				instance = new ConfigurationBasedSQLRegistry();
				
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		else
			instance = new ProgramBasedSQLRegistry();
	}

	protected Map<String, Object> registry;

	private static AbstractSQLRegistry instance;

	public AbstractSQLRegistry() {

		registry = new HashMap<String, Object>();
	}

	public static AbstractSQLRegistry getInstance() {

		return instance;
	}

	protected List<ArgumentWrapper> registerInsertStatement(Class<?> target)
			throws Exception {

		Field[] fields = target.getDeclaredFields();

		Field field = null;

		List<ArgumentWrapper> wrappers = new ArrayList<ArgumentWrapper>();

		ArgumentWrapper wrapper = null;

		Method[] methods = null;

		for (int i = 0; i < fields.length; i++) {

			field = fields[i];
			if (!field.isAnnotationPresent(PojoNonMapDeclaration.class)) {

				wrapper = new ArgumentWrapper();

				if (field
						.isAnnotationPresent(PojoForeignEntityDeclaration.class)) {

					methods = new Method[2];

					methods[0] = target.getClass().getMethod(
							"get" + nameToMethod(field.getName()));

					String key = field.getAnnotation(
							PojoForeignEntityDeclaration.class)
							.externalEntityColumnName();

					methods[1] = field.getType().getMethod(
							"get" + nameToMethod(key));

					wrapper.setTargetClass(filterToPrimitive(field.getType()
							.getDeclaredField(key).getType()));

					wrapper.setMethodArray(methods);

				} else {

					methods = new Method[1];

					methods[0] = target.getMethod("get"
							+ nameToMethod(field.getName()));

					wrapper.setMethodArray(methods);
					// get the actual variable type
					wrapper.setTargetClass(filterToPrimitive(field.getType()));

				}

				// get the actual set method within prepared statement
				wrapper
						.setPreparedStatementSetMethod(Class.forName(
								"java.sql.PreparedStatement").getMethod(
								"set"
										+ convertToMethodName(wrapper
												.getTargetClass().getName()),
								new Class[] { Integer.TYPE,
										wrapper.getTargetClass() }));

				wrapper.clean();

				wrappers.add(wrapper);
			}
		}

		return wrappers;
	}

	protected List<ArgumentWrapper> registerUpdateAndDeleteStatement(
			Class<?> target, Object[] vars, ParameterMapper[] mappers)
			throws Throwable {

		List<ArgumentWrapper> wrappers = new ArrayList<ArgumentWrapper>();

		ArgumentWrapper wrapper = null;

		ParameterMapper mapper = null;

		Method[] methods = null;

		String[] aliases = null;

		for (int j = 0; j < vars.length; j++) {

			mapper = (ParameterMapper) vars[j];

			wrapper = new ArgumentWrapper();
			wrapper.setIndex(mapper.getIndex());
			aliases = ((String) mapper.getParameter()).split("\\.");

			methods = new Method[aliases.length];

			nestedMethod(target, methods, aliases, 0);

			wrapper.setMethodArray(methods);

			// the return type of last method is the target
			wrapper
					.setTargetClass(filterToPrimitive(methods[methods.length - 1]
							.getReturnType()));

			wrapper.setPreparedStatementSetMethod(Class.forName(
					"java.sql.PreparedStatement").getMethod(
					"set"
							+ convertToMethodName(wrapper.getTargetClass()
									.getName()),
					new Class[] { Integer.TYPE, wrapper.getTargetClass() }));

			wrapper.clean();

			wrappers.add(wrapper);

		}
		if (null != mappers)
			for (int i = 0; i < mappers.length; i++) {

				mapper = mappers[i];
				if (mapper.getParameter() != null) {
					wrapper = new ArgumentWrapper();
					wrapper.setIndex(mapper.getIndex());
					aliases = ((String) mapper.getParameter()).split("\\.");

					methods = new Method[aliases.length];

					nestedMethod(target, methods, aliases, 0);

					wrapper.setMethodArray(methods);

					// the return type of last method is the target
					wrapper
							.setTargetClass(filterToPrimitive(methods[methods.length - 1]
									.getReturnType()));

					wrapper.setPreparedStatementSetMethod(Class.forName(
							"java.sql.PreparedStatement").getMethod(
							"set"
									+ convertToMethodName(wrapper
											.getTargetClass().getName()),
							new Class[] { Integer.TYPE,
									wrapper.getTargetClass() }));

					wrapper.clean();

					wrappers.add(wrapper);
				}
			}

		return wrappers;
	}




	public Object getRegisteredValue(String sql) {

		return registry.get(sql);
	}

	private Class<?> nestedMethod(Class<?> target, Method[] methods,
			String[] chain, int pop) throws Exception {

		methods[pop] = target.getMethod("get" + nameToMethod(chain[pop]));
		if (pop == chain.length - 1)
			return target.getDeclaredField(chain[pop]).getType();
		else
			return nestedMethod(target.getDeclaredField(chain[pop]).getType(),
					methods, chain, pop + 1);
	}

	public abstract void registerCreateSql(String sql, Class<?> target);

	public abstract void registerUpdateSql(String sql, Class<?> target,
			ParameterMapper[] mappers);

	public abstract void registerDeleteSql(String sql, Class<?> target,
			ParameterMapper[] mappers);

}
